/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management.remote.rmi;

import java.io.IOException;
import java.rmi.MarshalledObject;
import java.rmi.UnmarshalException;
import java.rmi.server.Unreferenced;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

import javax.management.*;
import javax.management.remote.JMXServerErrorException;
import javax.management.remote.NotificationResult;
import javax.management.remote.TargetedNotification;
import javax.security.auth.Subject;
import sun.reflect.misc.ReflectUtil;

import static com.sun.jmx.mbeanserver.Util.cast;

import com.sun.jmx.remote.internal.ServerCommunicatorAdmin;
import com.sun.jmx.remote.internal.ServerNotifForwarder;
import com.sun.jmx.remote.security.JMXSubjectDomainCombiner;
import com.sun.jmx.remote.security.SubjectDelegator;
import com.sun.jmx.remote.util.ClassLoaderWithRepository;
import com.sun.jmx.remote.util.ClassLogger;
import com.sun.jmx.remote.util.EnvHelp;
import com.sun.jmx.remote.util.OrderClassLoaders;
import javax.management.loading.ClassLoaderRepository;

/**
 * <p>Implementation of the {@link RMIConnection} interface.  User
 * code will not usually reference this class.</p>
 *
 * @since 1.5
 */
/*
 * Notice that we omit the type parameter from MarshalledObject everywhere,
 * even though it would add useful information to the documentation.  The
 * reason is that it was only added in Mustang (Java SE 6), whereas versions
 * 1.4 and 2.0 of the JMX API must be implementable on Tiger per our
 * commitments for JSR 255.
 */
public class RMIConnectionImpl implements RMIConnection, Unreferenced {

  /**
   * Constructs a new {@link RMIConnection}. This connection can be
   * used with either the JRMP or IIOP transport. This object does
   * not export itself: it is the responsibility of the caller to
   * export it appropriately (see {@link
   * RMIJRMPServerImpl#makeClient(String, Subject)} and {@link
   * RMIIIOPServerImpl#makeClient(String, Subject)}.
   *
   * @param rmiServer The RMIServerImpl object for which this connection is created.  The behavior
   * is unspecified if this parameter is null.
   * @param connectionId The ID for this connection.  The behavior is unspecified if this parameter
   * is null.
   * @param defaultClassLoader The default ClassLoader to be used when deserializing marshalled
   * objects.  Can be null, to signify the bootstrap class loader.
   * @param subject the authenticated subject to be used for authorization.  Can be null, to signify
   * that no subject has been authenticated.
   * @param env the environment containing attributes for the new <code>RMIServerImpl</code>.  Can
   * be null, equivalent to an empty map.
   */
  public RMIConnectionImpl(RMIServerImpl rmiServer,
      String connectionId,
      ClassLoader defaultClassLoader,
      Subject subject,
      Map<String, ?> env) {
    if (rmiServer == null || connectionId == null) {
      throw new NullPointerException("Illegal null argument");
    }
    if (env == null) {
      env = Collections.emptyMap();
    }
    this.rmiServer = rmiServer;
    this.connectionId = connectionId;
    this.defaultClassLoader = defaultClassLoader;

    this.subjectDelegator = new SubjectDelegator();
    this.subject = subject;
    if (subject == null) {
      this.acc = null;
      this.removeCallerContext = false;
    } else {
      this.removeCallerContext =
          SubjectDelegator.checkRemoveCallerContext(subject);
      if (this.removeCallerContext) {
        this.acc =
            JMXSubjectDomainCombiner.getDomainCombinerContext(subject);
      } else {
        this.acc =
            JMXSubjectDomainCombiner.getContext(subject);
      }
    }
    this.mbeanServer = rmiServer.getMBeanServer();

    final ClassLoader dcl = defaultClassLoader;

    ClassLoaderRepository repository = AccessController.doPrivileged(
        new PrivilegedAction<ClassLoaderRepository>() {
          public ClassLoaderRepository run() {
            return mbeanServer.getClassLoaderRepository();
          }
        },
        withPermissions(new MBeanPermission("*", "getClassLoaderRepository"))
    );
    this.classLoaderWithRepository = AccessController.doPrivileged(
        new PrivilegedAction<ClassLoaderWithRepository>() {
          public ClassLoaderWithRepository run() {
            return new ClassLoaderWithRepository(
                repository,
                dcl);
          }
        },
        withPermissions(new RuntimePermission("createClassLoader"))
    );

    this.defaultContextClassLoader =
        AccessController.doPrivileged(
            new PrivilegedAction<ClassLoader>() {
              @Override
              public ClassLoader run() {
                return new CombinedClassLoader(Thread.currentThread().getContextClassLoader(),
                    dcl);
              }
            });

    serverCommunicatorAdmin = new
        RMIServerCommunicatorAdmin(EnvHelp.getServerConnectionTimeout(env));

    this.env = env;
  }

  private static AccessControlContext withPermissions(Permission... perms) {
    Permissions col = new Permissions();

    for (Permission thePerm : perms) {
      col.add(thePerm);
    }

    final ProtectionDomain pd = new ProtectionDomain(null, col);
    return new AccessControlContext(new ProtectionDomain[]{pd});
  }

  private synchronized ServerNotifForwarder getServerNotifFwd() {
    // Lazily created when first use. Mainly when
    // addNotificationListener is first called.
    if (serverNotifForwarder == null) {
      serverNotifForwarder =
          new ServerNotifForwarder(mbeanServer,
              env,
              rmiServer.getNotifBuffer(),
              connectionId);
    }
    return serverNotifForwarder;
  }

  public String getConnectionId() throws IOException {
    // We should call reqIncomming() here... shouldn't we?
    return connectionId;
  }

  public void close() throws IOException {
    final boolean debug = logger.debugOn();
    final String idstr = (debug ? "[" + this.toString() + "]" : null);

    synchronized (this) {
      if (terminated) {
        if (debug) {
          logger.debug("close", idstr + " already terminated.");
        }
        return;
      }

      if (debug) {
        logger.debug("close", idstr + " closing.");
      }

      terminated = true;

      if (serverCommunicatorAdmin != null) {
        serverCommunicatorAdmin.terminate();
      }

      if (serverNotifForwarder != null) {
        serverNotifForwarder.terminate();
      }
    }

    rmiServer.clientClosed(this);

    if (debug) {
      logger.debug("close", idstr + " closed.");
    }
  }

  public void unreferenced() {
    logger.debug("unreferenced", "called");
    try {
      close();
      logger.debug("unreferenced", "done");
    } catch (IOException e) {
      logger.fine("unreferenced", e);
    }
  }

  //-------------------------------------------------------------------------
  // MBeanServerConnection Wrapper
  //-------------------------------------------------------------------------

  public ObjectInstance createMBean(String className,
      ObjectName name,
      Subject delegationSubject)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      IOException {
    try {
      final Object params[] =
          new Object[]{className, name};

      if (logger.debugOn()) {
        logger.debug("createMBean(String,ObjectName)",
            "connectionId=" + connectionId + ", className=" +
                className + ", name=" + name);
      }

      return (ObjectInstance)
          doPrivilegedOperation(
              CREATE_MBEAN,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof InstanceAlreadyExistsException) {
        throw (InstanceAlreadyExistsException) e;
      }
      if (e instanceof MBeanRegistrationException) {
        throw (MBeanRegistrationException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof NotCompliantMBeanException) {
        throw (NotCompliantMBeanException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public ObjectInstance createMBean(String className,
      ObjectName name,
      ObjectName loaderName,
      Subject delegationSubject)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      InstanceNotFoundException,
      IOException {
    try {
      final Object params[] =
          new Object[]{className, name, loaderName};

      if (logger.debugOn()) {
        logger.debug("createMBean(String,ObjectName,ObjectName)",
            "connectionId=" + connectionId
                + ", className=" + className
                + ", name=" + name
                + ", loaderName=" + loaderName);
      }

      return (ObjectInstance)
          doPrivilegedOperation(
              CREATE_MBEAN_LOADER,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof InstanceAlreadyExistsException) {
        throw (InstanceAlreadyExistsException) e;
      }
      if (e instanceof MBeanRegistrationException) {
        throw (MBeanRegistrationException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof NotCompliantMBeanException) {
        throw (NotCompliantMBeanException) e;
      }
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public ObjectInstance createMBean(String className,
      ObjectName name,
      MarshalledObject params,
      String signature[],
      Subject delegationSubject)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      IOException {

    final Object[] values;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug(
          "createMBean(String,ObjectName,Object[],String[])",
          "connectionId=" + connectionId
              + ", unwrapping parameters using classLoaderWithRepository.");
    }

    values =
        nullIsEmpty(unwrap(params, classLoaderWithRepository, Object[].class));

    try {
      final Object params2[] =
          new Object[]{className, name, values,
              nullIsEmpty(signature)};

      if (debug) {
        logger.debug("createMBean(String,ObjectName,Object[],String[])",
            "connectionId=" + connectionId
                + ", className=" + className
                + ", name=" + name
                + ", params=" + objects(values)
                + ", signature=" + strings(signature));
      }

      return (ObjectInstance)
          doPrivilegedOperation(
              CREATE_MBEAN_PARAMS,
              params2,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof InstanceAlreadyExistsException) {
        throw (InstanceAlreadyExistsException) e;
      }
      if (e instanceof MBeanRegistrationException) {
        throw (MBeanRegistrationException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof NotCompliantMBeanException) {
        throw (NotCompliantMBeanException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public ObjectInstance createMBean(String className,
      ObjectName name,
      ObjectName loaderName,
      MarshalledObject params,
      String signature[],
      Subject delegationSubject)
      throws
      ReflectionException,
      InstanceAlreadyExistsException,
      MBeanRegistrationException,
      MBeanException,
      NotCompliantMBeanException,
      InstanceNotFoundException,
      IOException {

    final Object[] values;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug(
          "createMBean(String,ObjectName,ObjectName,Object[],String[])",
          "connectionId=" + connectionId
              + ", unwrapping params with MBean extended ClassLoader.");
    }

    values = nullIsEmpty(unwrap(params,
        getClassLoader(loaderName),
        defaultClassLoader,
        Object[].class));

    try {
      final Object params2[] =
          new Object[]{className, name, loaderName, values,
              nullIsEmpty(signature)};

      if (debug) {
        logger.debug(
            "createMBean(String,ObjectName,ObjectName,Object[],String[])",
            "connectionId=" + connectionId
                + ", className=" + className
                + ", name=" + name
                + ", loaderName=" + loaderName
                + ", params=" + objects(values)
                + ", signature=" + strings(signature));
      }

      return (ObjectInstance)
          doPrivilegedOperation(
              CREATE_MBEAN_LOADER_PARAMS,
              params2,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof InstanceAlreadyExistsException) {
        throw (InstanceAlreadyExistsException) e;
      }
      if (e instanceof MBeanRegistrationException) {
        throw (MBeanRegistrationException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof NotCompliantMBeanException) {
        throw (NotCompliantMBeanException) e;
      }
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public void unregisterMBean(ObjectName name, Subject delegationSubject)
      throws
      InstanceNotFoundException,
      MBeanRegistrationException,
      IOException {
    try {
      final Object params[] = new Object[]{name};

      if (logger.debugOn()) {
        logger.debug("unregisterMBean",
            "connectionId=" + connectionId
                + ", name=" + name);
      }

      doPrivilegedOperation(
          UNREGISTER_MBEAN,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof MBeanRegistrationException) {
        throw (MBeanRegistrationException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public ObjectInstance getObjectInstance(ObjectName name,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      IOException {

    checkNonNull("ObjectName", name);

    try {
      final Object params[] = new Object[]{name};

      if (logger.debugOn()) {
        logger.debug("getObjectInstance",
            "connectionId=" + connectionId
                + ", name=" + name);
      }

      return (ObjectInstance)
          doPrivilegedOperation(
              GET_OBJECT_INSTANCE,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public Set<ObjectInstance>
  queryMBeans(ObjectName name,
      MarshalledObject query,
      Subject delegationSubject)
      throws IOException {
    final QueryExp queryValue;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug("queryMBeans",
          "connectionId=" + connectionId
              + " unwrapping query with defaultClassLoader.");
    }

    queryValue = unwrap(query, defaultContextClassLoader, QueryExp.class);

    try {
      final Object params[] = new Object[]{name, queryValue};

      if (debug) {
        logger.debug("queryMBeans",
            "connectionId=" + connectionId
                + ", name=" + name + ", query=" + query);
      }

      return cast(
          doPrivilegedOperation(
              QUERY_MBEANS,
              params,
              delegationSubject));
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public Set<ObjectName>
  queryNames(ObjectName name,
      MarshalledObject query,
      Subject delegationSubject)
      throws IOException {
    final QueryExp queryValue;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug("queryNames",
          "connectionId=" + connectionId
              + " unwrapping query with defaultClassLoader.");
    }

    queryValue = unwrap(query, defaultContextClassLoader, QueryExp.class);

    try {
      final Object params[] = new Object[]{name, queryValue};

      if (debug) {
        logger.debug("queryNames",
            "connectionId=" + connectionId
                + ", name=" + name + ", query=" + query);
      }

      return cast(
          doPrivilegedOperation(
              QUERY_NAMES,
              params,
              delegationSubject));
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public boolean isRegistered(ObjectName name,
      Subject delegationSubject) throws IOException {
    try {
      final Object params[] = new Object[]{name};
      return ((Boolean)
          doPrivilegedOperation(
              IS_REGISTERED,
              params,
              delegationSubject)).booleanValue();
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public Integer getMBeanCount(Subject delegationSubject)
      throws IOException {
    try {
      final Object params[] = new Object[]{};

      if (logger.debugOn()) {
        logger.debug("getMBeanCount",
            "connectionId=" + connectionId);
      }

      return (Integer)
          doPrivilegedOperation(
              GET_MBEAN_COUNT,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public Object getAttribute(ObjectName name,
      String attribute,
      Subject delegationSubject)
      throws
      MBeanException,
      AttributeNotFoundException,
      InstanceNotFoundException,
      ReflectionException,
      IOException {
    try {
      final Object params[] = new Object[]{name, attribute};
      if (logger.debugOn()) {
        logger.debug("getAttribute",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", attribute=" + attribute);
      }

      return
          doPrivilegedOperation(
              GET_ATTRIBUTE,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof AttributeNotFoundException) {
        throw (AttributeNotFoundException) e;
      }
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public AttributeList getAttributes(ObjectName name,
      String[] attributes,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      ReflectionException,
      IOException {
    try {
      final Object params[] = new Object[]{name, attributes};

      if (logger.debugOn()) {
        logger.debug("getAttributes",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", attributes=" + strings(attributes));
      }

      return (AttributeList)
          doPrivilegedOperation(
              GET_ATTRIBUTES,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public void setAttribute(ObjectName name,
      MarshalledObject attribute,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      AttributeNotFoundException,
      InvalidAttributeValueException,
      MBeanException,
      ReflectionException,
      IOException {
    final Attribute attr;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug("setAttribute",
          "connectionId=" + connectionId
              + " unwrapping attribute with MBean extended ClassLoader.");
    }

    attr = unwrap(attribute,
        getClassLoaderFor(name),
        defaultClassLoader,
        Attribute.class);

    try {
      final Object params[] = new Object[]{name, attr};

      if (debug) {
        logger.debug("setAttribute",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", attribute=" + attr);
      }

      doPrivilegedOperation(
          SET_ATTRIBUTE,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof AttributeNotFoundException) {
        throw (AttributeNotFoundException) e;
      }
      if (e instanceof InvalidAttributeValueException) {
        throw (InvalidAttributeValueException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public AttributeList setAttributes(ObjectName name,
      MarshalledObject attributes,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      ReflectionException,
      IOException {
    final AttributeList attrlist;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug("setAttributes",
          "connectionId=" + connectionId
              + " unwrapping attributes with MBean extended ClassLoader.");
    }

    attrlist =
        unwrap(attributes,
            getClassLoaderFor(name),
            defaultClassLoader,
            AttributeList.class);

    try {
      final Object params[] = new Object[]{name, attrlist};

      if (debug) {
        logger.debug("setAttributes",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", attributes=" + attrlist);
      }

      return (AttributeList)
          doPrivilegedOperation(
              SET_ATTRIBUTES,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public Object invoke(ObjectName name,
      String operationName,
      MarshalledObject params,
      String signature[],
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      MBeanException,
      ReflectionException,
      IOException {

    checkNonNull("ObjectName", name);
    checkNonNull("Operation name", operationName);

    final Object[] values;
    final boolean debug = logger.debugOn();

    if (debug) {
      logger.debug("invoke",
          "connectionId=" + connectionId
              + " unwrapping params with MBean extended ClassLoader.");
    }

    values = nullIsEmpty(unwrap(params,
        getClassLoaderFor(name),
        defaultClassLoader,
        Object[].class));

    try {
      final Object params2[] =
          new Object[]{name, operationName, values,
              nullIsEmpty(signature)};

      if (debug) {
        logger.debug("invoke",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", operationName=" + operationName
                + ", params=" + objects(values)
                + ", signature=" + strings(signature));
      }

      return
          doPrivilegedOperation(
              INVOKE,
              params2,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof MBeanException) {
        throw (MBeanException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public String getDefaultDomain(Subject delegationSubject)
      throws IOException {
    try {
      final Object params[] = new Object[]{};

      if (logger.debugOn()) {
        logger.debug("getDefaultDomain",
            "connectionId=" + connectionId);
      }

      return (String)
          doPrivilegedOperation(
              GET_DEFAULT_DOMAIN,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public String[] getDomains(Subject delegationSubject) throws IOException {
    try {
      final Object params[] = new Object[]{};

      if (logger.debugOn()) {
        logger.debug("getDomains",
            "connectionId=" + connectionId);
      }

      return (String[])
          doPrivilegedOperation(
              GET_DOMAINS,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public MBeanInfo getMBeanInfo(ObjectName name, Subject delegationSubject)
      throws
      InstanceNotFoundException,
      IntrospectionException,
      ReflectionException,
      IOException {

    checkNonNull("ObjectName", name);

    try {
      final Object params[] = new Object[]{name};

      if (logger.debugOn()) {
        logger.debug("getMBeanInfo",
            "connectionId=" + connectionId
                + ", name=" + name);
      }

      return (MBeanInfo)
          doPrivilegedOperation(
              GET_MBEAN_INFO,
              params,
              delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IntrospectionException) {
        throw (IntrospectionException) e;
      }
      if (e instanceof ReflectionException) {
        throw (ReflectionException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public boolean isInstanceOf(ObjectName name,
      String className,
      Subject delegationSubject)
      throws InstanceNotFoundException, IOException {

    checkNonNull("ObjectName", name);

    try {
      final Object params[] = new Object[]{name, className};

      if (logger.debugOn()) {
        logger.debug("isInstanceOf",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", className=" + className);
      }

      return ((Boolean)
          doPrivilegedOperation(
              IS_INSTANCE_OF,
              params,
              delegationSubject)).booleanValue();
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public Integer[] addNotificationListeners(ObjectName[] names,
      MarshalledObject[] filters,
      Subject[] delegationSubjects)
      throws InstanceNotFoundException, IOException {

    if (names == null || filters == null) {
      throw new IllegalArgumentException("Got null arguments.");
    }

    Subject[] sbjs = (delegationSubjects != null) ? delegationSubjects :
        new Subject[names.length];
    if (names.length != filters.length || filters.length != sbjs.length) {
      final String msg =
          "The value lengths of 3 parameters are not same.";
      throw new IllegalArgumentException(msg);
    }

    for (int i = 0; i < names.length; i++) {
      if (names[i] == null) {
        throw new IllegalArgumentException("Null Object name.");
      }
    }

    int i = 0;
    ClassLoader targetCl;
    NotificationFilter[] filterValues =
        new NotificationFilter[names.length];
    Integer[] ids = new Integer[names.length];
    final boolean debug = logger.debugOn();

    try {
      for (; i < names.length; i++) {
        targetCl = getClassLoaderFor(names[i]);

        if (debug) {
          logger.debug("addNotificationListener" +
                  "(ObjectName,NotificationFilter)",
              "connectionId=" + connectionId +
                  " unwrapping filter with target extended ClassLoader.");
        }

        filterValues[i] =
            unwrap(filters[i], targetCl, defaultClassLoader,
                NotificationFilter.class);

        if (debug) {
          logger.debug("addNotificationListener" +
                  "(ObjectName,NotificationFilter)",
              "connectionId=" + connectionId
                  + ", name=" + names[i]
                  + ", filter=" + filterValues[i]);
        }

        ids[i] = (Integer)
            doPrivilegedOperation(ADD_NOTIFICATION_LISTENERS,
                new Object[]{names[i],
                    filterValues[i]},
                sbjs[i]);
      }

      return ids;
    } catch (Exception e) {
      // remove all registered listeners
      for (int j = 0; j < i; j++) {
        try {
          getServerNotifFwd().removeNotificationListener(names[j],
              ids[j]);
        } catch (Exception eee) {
          // strange
        }
      }

      if (e instanceof PrivilegedActionException) {
        e = extractException(e);
      }

      if (e instanceof ClassCastException) {
        throw (ClassCastException) e;
      } else if (e instanceof IOException) {
        throw (IOException) e;
      } else if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      } else if (e instanceof RuntimeException) {
        throw (RuntimeException) e;
      } else {
        throw newIOException("Got unexpected server exception: " + e, e);
      }
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public void addNotificationListener(ObjectName name,
      ObjectName listener,
      MarshalledObject filter,
      MarshalledObject handback,
      Subject delegationSubject)
      throws InstanceNotFoundException, IOException {

    checkNonNull("Target MBean name", name);
    checkNonNull("Listener MBean name", listener);

    final NotificationFilter filterValue;
    final Object handbackValue;
    final boolean debug = logger.debugOn();

    final ClassLoader targetCl = getClassLoaderFor(name);

    if (debug) {
      logger.debug("addNotificationListener" +
              "(ObjectName,ObjectName,NotificationFilter,Object)",
          "connectionId=" + connectionId
              + " unwrapping filter with target extended ClassLoader.");
    }

    filterValue =
        unwrap(filter, targetCl, defaultClassLoader, NotificationFilter.class);

    if (debug) {
      logger.debug("addNotificationListener" +
              "(ObjectName,ObjectName,NotificationFilter,Object)",
          "connectionId=" + connectionId
              + " unwrapping handback with target extended ClassLoader.");
    }

    handbackValue =
        unwrap(handback, targetCl, defaultClassLoader, Object.class);

    try {
      final Object params[] =
          new Object[]{name, listener, filterValue, handbackValue};

      if (debug) {
        logger.debug("addNotificationListener" +
                "(ObjectName,ObjectName,NotificationFilter,Object)",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", listenerName=" + listener
                + ", filter=" + filterValue
                + ", handback=" + handbackValue);
      }

      doPrivilegedOperation(
          ADD_NOTIFICATION_LISTENER_OBJECTNAME,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public void removeNotificationListeners(ObjectName name,
      Integer[] listenerIDs,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      ListenerNotFoundException,
      IOException {

    if (name == null || listenerIDs == null) {
      throw new IllegalArgumentException("Illegal null parameter");
    }

    for (int i = 0; i < listenerIDs.length; i++) {
      if (listenerIDs[i] == null) {
        throw new IllegalArgumentException("Null listener ID");
      }
    }

    try {
      final Object params[] = new Object[]{name, listenerIDs};

      if (logger.debugOn()) {
        logger.debug("removeNotificationListener" +
                "(ObjectName,Integer[])",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", listenerIDs=" + objects(listenerIDs));
      }

      doPrivilegedOperation(
          REMOVE_NOTIFICATION_LISTENER,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ListenerNotFoundException) {
        throw (ListenerNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public void removeNotificationListener(ObjectName name,
      ObjectName listener,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      ListenerNotFoundException,
      IOException {

    checkNonNull("Target MBean name", name);
    checkNonNull("Listener MBean name", listener);

    try {
      final Object params[] = new Object[]{name, listener};

      if (logger.debugOn()) {
        logger.debug("removeNotificationListener" +
                "(ObjectName,ObjectName)",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", listenerName=" + listener);
      }

      doPrivilegedOperation(
          REMOVE_NOTIFICATION_LISTENER_OBJECTNAME,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ListenerNotFoundException) {
        throw (ListenerNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  @SuppressWarnings("rawtypes")  // MarshalledObject
  public void removeNotificationListener(ObjectName name,
      ObjectName listener,
      MarshalledObject filter,
      MarshalledObject handback,
      Subject delegationSubject)
      throws
      InstanceNotFoundException,
      ListenerNotFoundException,
      IOException {

    checkNonNull("Target MBean name", name);
    checkNonNull("Listener MBean name", listener);

    final NotificationFilter filterValue;
    final Object handbackValue;
    final boolean debug = logger.debugOn();

    final ClassLoader targetCl = getClassLoaderFor(name);

    if (debug) {
      logger.debug("removeNotificationListener" +
              "(ObjectName,ObjectName,NotificationFilter,Object)",
          "connectionId=" + connectionId
              + " unwrapping filter with target extended ClassLoader.");
    }

    filterValue =
        unwrap(filter, targetCl, defaultClassLoader, NotificationFilter.class);

    if (debug) {
      logger.debug("removeNotificationListener" +
              "(ObjectName,ObjectName,NotificationFilter,Object)",
          "connectionId=" + connectionId
              + " unwrapping handback with target extended ClassLoader.");
    }

    handbackValue =
        unwrap(handback, targetCl, defaultClassLoader, Object.class);

    try {
      final Object params[] =
          new Object[]{name, listener, filterValue, handbackValue};

      if (debug) {
        logger.debug("removeNotificationListener" +
                "(ObjectName,ObjectName,NotificationFilter,Object)",
            "connectionId=" + connectionId
                + ", name=" + name
                + ", listenerName=" + listener
                + ", filter=" + filterValue
                + ", handback=" + handbackValue);
      }

      doPrivilegedOperation(
          REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK,
          params,
          delegationSubject);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof InstanceNotFoundException) {
        throw (InstanceNotFoundException) e;
      }
      if (e instanceof ListenerNotFoundException) {
        throw (ListenerNotFoundException) e;
      }
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      throw newIOException("Got unexpected server exception: " + e, e);
    }
  }

  public NotificationResult fetchNotifications(long clientSequenceNumber,
      int maxNotifications,
      long timeout)
      throws IOException {

    if (logger.debugOn()) {
      logger.debug("fetchNotifications",
          "connectionId=" + connectionId
              + ", timeout=" + timeout);
    }

    if (maxNotifications < 0 || timeout < 0) {
      throw new IllegalArgumentException("Illegal negative argument");
    }

    final boolean serverTerminated =
        serverCommunicatorAdmin.reqIncoming();
    try {
      if (serverTerminated) {
        // we must not call fetchNotifs() if the server is
        // terminated (timeout elapsed).
        // returns null to force the client to stop fetching
        if (logger.debugOn()) {
          logger.debug("fetchNotifications",
              "The notification server has been closed, "
                  + "returns null to force the client to stop fetching");
        }
        return null;
      }
      final long csn = clientSequenceNumber;
      final int mn = maxNotifications;
      final long t = timeout;
      PrivilegedAction<NotificationResult> action =
          new PrivilegedAction<NotificationResult>() {
            public NotificationResult run() {
              return getServerNotifFwd().fetchNotifs(csn, t, mn);
            }
          };
      if (acc == null) {
        return action.run();
      } else {
        return AccessController.doPrivileged(action, acc);
      }
    } finally {
      serverCommunicatorAdmin.rspOutgoing();
    }
  }

  /**
   * <p>Returns a string representation of this object.  In general,
   * the <code>toString</code> method returns a string that
   * "textually represents" this object. The result should be a
   * concise but informative representation that is easy for a
   * person to read.</p>
   *
   * @return a String representation of this object.
   **/
  @Override
  public String toString() {
    return super.toString() + ": connectionId=" + connectionId;
  }

  //------------------------------------------------------------------------
  // private classes
  //------------------------------------------------------------------------

  private class PrivilegedOperation
      implements PrivilegedExceptionAction<Object> {

    public PrivilegedOperation(int operation, Object[] params) {
      this.operation = operation;
      this.params = params;
    }

    public Object run() throws Exception {
      return doOperation(operation, params);
    }

    private int operation;
    private Object[] params;
  }

  //------------------------------------------------------------------------
  // private classes
  //------------------------------------------------------------------------
  private class RMIServerCommunicatorAdmin extends ServerCommunicatorAdmin {

    public RMIServerCommunicatorAdmin(long timeout) {
      super(timeout);
    }

    protected void doStop() {
      try {
        close();
      } catch (IOException ie) {
        logger.warning("RMIServerCommunicatorAdmin-doStop",
            "Failed to close: " + ie);
        logger.debug("RMIServerCommunicatorAdmin-doStop", ie);
      }
    }

  }

  //------------------------------------------------------------------------
  // private methods
  //------------------------------------------------------------------------

  private ClassLoader getClassLoader(final ObjectName name)
      throws InstanceNotFoundException {
    try {
      return
          AccessController.doPrivileged(
              new PrivilegedExceptionAction<ClassLoader>() {
                public ClassLoader run() throws InstanceNotFoundException {
                  return mbeanServer.getClassLoader(name);
                }
              },
              withPermissions(new MBeanPermission("*", "getClassLoader"))
          );
    } catch (PrivilegedActionException pe) {
      throw (InstanceNotFoundException) extractException(pe);
    }
  }

  private ClassLoader getClassLoaderFor(final ObjectName name)
      throws InstanceNotFoundException {
    try {
      return (ClassLoader)
          AccessController.doPrivileged(
              new PrivilegedExceptionAction<Object>() {
                public Object run() throws InstanceNotFoundException {
                  return mbeanServer.getClassLoaderFor(name);
                }
              },
              withPermissions(new MBeanPermission("*", "getClassLoaderFor"))
          );
    } catch (PrivilegedActionException pe) {
      throw (InstanceNotFoundException) extractException(pe);
    }
  }

  private Object doPrivilegedOperation(final int operation,
      final Object[] params,
      final Subject delegationSubject)
      throws PrivilegedActionException, IOException {

    serverCommunicatorAdmin.reqIncoming();
    try {

      final AccessControlContext reqACC;
      if (delegationSubject == null) {
        reqACC = acc;
      } else {
        if (subject == null) {
          final String msg =
              "Subject delegation cannot be enabled unless " +
                  "an authenticated subject is put in place";
          throw new SecurityException(msg);
        }
        reqACC = subjectDelegator.delegatedContext(
            acc, delegationSubject, removeCallerContext);
      }

      PrivilegedOperation op =
          new PrivilegedOperation(operation, params);
      if (reqACC == null) {
        try {
          return op.run();
        } catch (Exception e) {
          if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
          }
          throw new PrivilegedActionException(e);
        }
      } else {
        return AccessController.doPrivileged(op, reqACC);
      }
    } catch (Error e) {
      throw new JMXServerErrorException(e.toString(), e);
    } finally {
      serverCommunicatorAdmin.rspOutgoing();
    }
  }

  private Object doOperation(int operation, Object[] params)
      throws Exception {

    switch (operation) {

      case CREATE_MBEAN:
        return mbeanServer.createMBean((String) params[0],
            (ObjectName) params[1]);

      case CREATE_MBEAN_LOADER:
        return mbeanServer.createMBean((String) params[0],
            (ObjectName) params[1],
            (ObjectName) params[2]);

      case CREATE_MBEAN_PARAMS:
        return mbeanServer.createMBean((String) params[0],
            (ObjectName) params[1],
            (Object[]) params[2],
            (String[]) params[3]);

      case CREATE_MBEAN_LOADER_PARAMS:
        return mbeanServer.createMBean((String) params[0],
            (ObjectName) params[1],
            (ObjectName) params[2],
            (Object[]) params[3],
            (String[]) params[4]);

      case GET_ATTRIBUTE:
        return mbeanServer.getAttribute((ObjectName) params[0],
            (String) params[1]);

      case GET_ATTRIBUTES:
        return mbeanServer.getAttributes((ObjectName) params[0],
            (String[]) params[1]);

      case GET_DEFAULT_DOMAIN:
        return mbeanServer.getDefaultDomain();

      case GET_DOMAINS:
        return mbeanServer.getDomains();

      case GET_MBEAN_COUNT:
        return mbeanServer.getMBeanCount();

      case GET_MBEAN_INFO:
        return mbeanServer.getMBeanInfo((ObjectName) params[0]);

      case GET_OBJECT_INSTANCE:
        return mbeanServer.getObjectInstance((ObjectName) params[0]);

      case INVOKE:
        return mbeanServer.invoke((ObjectName) params[0],
            (String) params[1],
            (Object[]) params[2],
            (String[]) params[3]);

      case IS_INSTANCE_OF:
        return mbeanServer.isInstanceOf((ObjectName) params[0],
            (String) params[1])
            ? Boolean.TRUE : Boolean.FALSE;

      case IS_REGISTERED:
        return mbeanServer.isRegistered((ObjectName) params[0])
            ? Boolean.TRUE : Boolean.FALSE;

      case QUERY_MBEANS:
        return mbeanServer.queryMBeans((ObjectName) params[0],
            (QueryExp) params[1]);

      case QUERY_NAMES:
        return mbeanServer.queryNames((ObjectName) params[0],
            (QueryExp) params[1]);

      case SET_ATTRIBUTE:
        mbeanServer.setAttribute((ObjectName) params[0],
            (Attribute) params[1]);
        return null;

      case SET_ATTRIBUTES:
        return mbeanServer.setAttributes((ObjectName) params[0],
            (AttributeList) params[1]);

      case UNREGISTER_MBEAN:
        mbeanServer.unregisterMBean((ObjectName) params[0]);
        return null;

      case ADD_NOTIFICATION_LISTENERS:
        return getServerNotifFwd().addNotificationListener(
            (ObjectName) params[0],
            (NotificationFilter) params[1]);

      case ADD_NOTIFICATION_LISTENER_OBJECTNAME:
        mbeanServer.addNotificationListener((ObjectName) params[0],
            (ObjectName) params[1],
            (NotificationFilter) params[2],
            params[3]);
        return null;

      case REMOVE_NOTIFICATION_LISTENER:
        getServerNotifFwd().removeNotificationListener(
            (ObjectName) params[0],
            (Integer[]) params[1]);
        return null;

      case REMOVE_NOTIFICATION_LISTENER_OBJECTNAME:
        mbeanServer.removeNotificationListener((ObjectName) params[0],
            (ObjectName) params[1]);
        return null;

      case REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK:
        mbeanServer.removeNotificationListener(
            (ObjectName) params[0],
            (ObjectName) params[1],
            (NotificationFilter) params[2],
            params[3]);
        return null;

      default:
        throw new IllegalArgumentException("Invalid operation");
    }
  }

  private static class SetCcl implements PrivilegedExceptionAction<ClassLoader> {

    private final ClassLoader classLoader;

    SetCcl(ClassLoader classLoader) {
      this.classLoader = classLoader;
    }

    public ClassLoader run() {
      Thread currentThread = Thread.currentThread();
      ClassLoader old = currentThread.getContextClassLoader();
      currentThread.setContextClassLoader(classLoader);
      return old;
    }
  }

  private static <T> T unwrap(final MarshalledObject<?> mo,
      final ClassLoader cl,
      final Class<T> wrappedClass)
      throws IOException {
    if (mo == null) {
      return null;
    }
    try {
      final ClassLoader old = AccessController.doPrivileged(new SetCcl(cl));
      try {
        return wrappedClass.cast(mo.get());
      } catch (ClassNotFoundException cnfe) {
        throw new UnmarshalException(cnfe.toString(), cnfe);
      } finally {
        AccessController.doPrivileged(new SetCcl(old));
      }
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      if (e instanceof ClassNotFoundException) {
        throw new UnmarshalException(e.toString(), e);
      }
      logger.warning("unwrap", "Failed to unmarshall object: " + e);
      logger.debug("unwrap", e);
    }
    return null;
  }

  private static <T> T unwrap(final MarshalledObject<?> mo,
      final ClassLoader cl1,
      final ClassLoader cl2,
      final Class<T> wrappedClass)
      throws IOException {
    if (mo == null) {
      return null;
    }
    try {
      ClassLoader orderCL = AccessController.doPrivileged(
          new PrivilegedExceptionAction<ClassLoader>() {
            public ClassLoader run() throws Exception {
              return new CombinedClassLoader(Thread.currentThread().getContextClassLoader(),
                  new OrderClassLoaders(cl1, cl2));
            }
          }
      );
      return unwrap(mo, orderCL, wrappedClass);
    } catch (PrivilegedActionException pe) {
      Exception e = extractException(pe);
      if (e instanceof IOException) {
        throw (IOException) e;
      }
      if (e instanceof ClassNotFoundException) {
        throw new UnmarshalException(e.toString(), e);
      }
      logger.warning("unwrap", "Failed to unmarshall object: " + e);
      logger.debug("unwrap", e);
    }
    return null;
  }

  /**
   * Construct a new IOException with a nested exception.
   * The nested exception is set only if JDK {@literal >= 1.4}
   */
  private static IOException newIOException(String message,
      Throwable cause) {
    final IOException x = new IOException(message);
    return EnvHelp.initCause(x, cause);
  }

  /**
   * Iterate until we extract the real exception
   * from a stack of PrivilegedActionExceptions.
   */
  private static Exception extractException(Exception e) {
    while (e instanceof PrivilegedActionException) {
      e = ((PrivilegedActionException) e).getException();
    }
    return e;
  }

  private static final Object[] NO_OBJECTS = new Object[0];
  private static final String[] NO_STRINGS = new String[0];

  /*
   * The JMX spec doesn't explicitly say that a null Object[] or
   * String[] in e.g. MBeanServer.invoke is equivalent to an empty
   * array, but the RI behaves that way.  In the interests of
   * maximal interoperability, we make it so even when we're
   * connected to some other JMX implementation that might not do
   * that.  This should be clarified in the next version of JMX.
   */
  private static Object[] nullIsEmpty(Object[] array) {
    return (array == null) ? NO_OBJECTS : array;
  }

  private static String[] nullIsEmpty(String[] array) {
    return (array == null) ? NO_STRINGS : array;
  }

  /*
   * Similarly, the JMX spec says for some but not all methods in
   * MBeanServer that take an ObjectName target, that if it's null
   * you get this exception.  We specify it for all of them, and
   * make it so for the ones where it's not specified in JMX even if
   * the JMX implementation doesn't do so.
   */
  private static void checkNonNull(String what, Object x) {
    if (x == null) {
      RuntimeException wrapped =
          new IllegalArgumentException(what + " must not be null");
      throw new RuntimeOperationsException(wrapped);
    }
  }

  //------------------------------------------------------------------------
  // private variables
  //------------------------------------------------------------------------

  private final Subject subject;

  private final SubjectDelegator subjectDelegator;

  private final boolean removeCallerContext;

  private final AccessControlContext acc;

  private final RMIServerImpl rmiServer;

  private final MBeanServer mbeanServer;

  private final ClassLoader defaultClassLoader;

  private final ClassLoader defaultContextClassLoader;

  private final ClassLoaderWithRepository classLoaderWithRepository;

  private boolean terminated = false;

  private final String connectionId;

  private final ServerCommunicatorAdmin serverCommunicatorAdmin;

  // Method IDs for doOperation
  //---------------------------

  private final static int
      ADD_NOTIFICATION_LISTENERS = 1;
  private final static int
      ADD_NOTIFICATION_LISTENER_OBJECTNAME = 2;
  private final static int
      CREATE_MBEAN = 3;
  private final static int
      CREATE_MBEAN_PARAMS = 4;
  private final static int
      CREATE_MBEAN_LOADER = 5;
  private final static int
      CREATE_MBEAN_LOADER_PARAMS = 6;
  private final static int
      GET_ATTRIBUTE = 7;
  private final static int
      GET_ATTRIBUTES = 8;
  private final static int
      GET_DEFAULT_DOMAIN = 9;
  private final static int
      GET_DOMAINS = 10;
  private final static int
      GET_MBEAN_COUNT = 11;
  private final static int
      GET_MBEAN_INFO = 12;
  private final static int
      GET_OBJECT_INSTANCE = 13;
  private final static int
      INVOKE = 14;
  private final static int
      IS_INSTANCE_OF = 15;
  private final static int
      IS_REGISTERED = 16;
  private final static int
      QUERY_MBEANS = 17;
  private final static int
      QUERY_NAMES = 18;
  private final static int
      REMOVE_NOTIFICATION_LISTENER = 19;
  private final static int
      REMOVE_NOTIFICATION_LISTENER_OBJECTNAME = 20;
  private final static int
      REMOVE_NOTIFICATION_LISTENER_OBJECTNAME_FILTER_HANDBACK = 21;
  private final static int
      SET_ATTRIBUTE = 22;
  private final static int
      SET_ATTRIBUTES = 23;
  private final static int
      UNREGISTER_MBEAN = 24;

  // SERVER NOTIFICATION
  //--------------------

  private ServerNotifForwarder serverNotifForwarder;
  private Map<String, ?> env;

  // TRACES & DEBUG
  //---------------

  private static String objects(final Object[] objs) {
    if (objs == null) {
      return "null";
    } else {
      return Arrays.asList(objs).toString();
    }
  }

  private static String strings(final String[] strs) {
    return objects(strs);
  }

  private static final ClassLogger logger =
      new ClassLogger("javax.management.remote.rmi", "RMIConnectionImpl");

  private static final class CombinedClassLoader extends ClassLoader {

    private final static class ClassLoaderWrapper extends ClassLoader {

      ClassLoaderWrapper(ClassLoader cl) {
        super(cl);
      }

      @Override
      protected Class<?> loadClass(String name, boolean resolve)
          throws ClassNotFoundException {
        return super.loadClass(name, resolve);
      }
    }

    ;

    final ClassLoaderWrapper defaultCL;

    private CombinedClassLoader(ClassLoader parent, ClassLoader defaultCL) {
      super(parent);
      this.defaultCL = new ClassLoaderWrapper(defaultCL);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
      ReflectUtil.checkPackageAccess(name);
      try {
        super.loadClass(name, resolve);
      } catch (Exception e) {
        for (Throwable t = e; t != null; t = t.getCause()) {
          if (t instanceof SecurityException) {
            throw t == e ? (SecurityException) t : new SecurityException(t.getMessage(), e);
          }
        }
      }
      final Class<?> cl = defaultCL.loadClass(name, resolve);
      return cl;
    }

  }
}
